package com.jfinal.plugin.mysql;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.setting.Setting;
import com.jfinal.annotation.Model;
import com.jfinal.kit.PathKit;
import com.jfinal.kit.StrKit;
import com.jfinal.plugin.IPlugin;
import com.jfinal.plugin.activerecord.ActiveRecordPlugin;
import com.jfinal.plugin.activerecord.OrderedFieldContainerFactory;
import com.jfinal.plugin.druid.DruidPlugin;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.reflections.Reflections;
import org.reflections.scanners.ResourcesScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;

import java.net.URL;
import java.util.*;
import java.util.regex.Pattern;

/**
 * 配置文件必须是 ini 格式
 * <p>
 * # 文件格式
 * [mysql]
 * jdbcUrl = jdbc:mysql://127.0.0.1/wtde?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull
 * user = root
 * password =
 * devMode = true
 * </p>
 */
public class MysqlDataSourcePlugin implements IPlugin {

    protected Logger logger = LogManager.getLogger(getClass().getName());

    /**
     * 配置文件路径
     */
    private String configPath = "config/mysql.txt";

    private String stlRootPath = null;

    public MysqlDataSourcePlugin(){}

    public MysqlDataSourcePlugin(String configPath){
        this.configPath = configPath;
    }

    public MysqlDataSourcePlugin(String configPath, String stlRootPath){
        this.configPath = configPath;
        this.stlRootPath = stlRootPath;
    }

    
    /*public static void main(String[] args) {
        String public_key = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCKwm/RsmGN602mmTI3bYuLOKsm8i7m7RO2OgzHKFvymGLQCk/QgKOuGO0SfyWqtCrVw9fYgDJ5H/nlFGfP/I9P2tBtM2KgTZmHh5wxUakYBExgdREAOzHLjpMbeB7YxmWh5bM6/WTeuKZ4T+pfOxA7uLRl+dh+KrFtIXeZ6/YEwIDAQAB";
        String private_key = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMIrCb9GyYY3rTaaZMjdti4s4qybyLubtE7Y6DMcoW/KYYtAKT9CAo64Y7RJ/Jaq0KtXD19iAMnkf+eUUZ8/8j0/a0G0zYqBNmYeHnDFRqRgETGB1EQA7McuOkxt4HtjGZaHlszr9ZN64pnhP6l87EDu4tGX52H4qsW0hd5nr9gTAgMBAAECgYBl41usCaM6UQENxBra5Kzysg8IcH73I4+MSHogSAfWt6ZUAgki7qqU5eE7/A8VqPDy2C5y6ORtDQCpLsVeAZpE6WpyRIuYqiNb6wwQel6kXqXHfOTnf6r3/wABWZCo2svDFb/wq2K05vEVWrC8xyzEh0x0KEfCdbL8Y65GZWf6MQJBAP7iqxVK85jSVcsOR23iNxyC+j6B2GHikfINF908b5Oc0EZhXN3qMWzeSM+lrhg8jA7lNrYI0nTwkXf3A+cFJ48CQQDDBGZVcW3wFntSZK8580WqvlVB48jNITo1OU6EoEa9z4m5OoLsTENUFIM+QAR5xW4vnpfxou2vfMpJyd6ChmU9AkEA6A1RmRcd5BpKxY2AdogniKuuTM5GRAfUu7wz59Wn94ojDJCDzf3zdZyCSZobgDFPihgg0WYCxThoGAIp6WAF/QJAbJIYft0lfZKmmZpgS3z6fjJ0uLgd5MXo6BS9d+zbNXVDkZHXrcJPgZHh91ggpcKas3wxz18LwLVS0X3HmRdFaQJAIph2hL2vhYLxzTAVrn1dVqj8+rrKCH3bD8PPl8uuYgLrIouk3lNOEHTpbqjjlV6e7Umpi8L5VeeACPzBPLUhqg==";

        RSA rsa = new RSA(private_key,public_key);
         *//*加密*//*
        byte[] encrypt = rsa.encrypt(StrUtil.bytes("root", CharsetUtil.CHARSET_UTF_8), KeyType.PublicKey);
         *//*解密*//*
        //byte[] decrypt = rsa.decrypt(encrypt, KeyType.PrivateKey);
        byte[] decrypt = rsa.decryptFromBase64(Base64.encode(encrypt),KeyType.PrivateKey);

        //Assert.assertEquals("我是一段测试aaaa", );
        System.out.println(Base64.encode(encrypt));
        System.out.println(StrUtil.str(decrypt, CharsetUtil.CHARSET_UTF_8));
    }*/
    
    
    
    /**
     * 创建连接池
     * @param config
     * @return
     */
    public DruidPlugin createDruidPlugin(Setting config){
        /**
         * 相关属性解密处理
         */
        if(StrKit.isBlank(config.getStr("rsa.privateKey")) && StrKit.isBlank(config.getStr("rsa.fields"))){
            /* 获取私钥 */
            String privateKey = config.getStr("rsa.privateKey").trim();
            /* 获得加密的列 */
            String[] fields = config.getStrings("rsa.fields");

            RSA rsa = new RSA(privateKey,null);
            for(String field : fields){
                /* 重新赋值 */
                field = field.trim();
                if(!StrKit.isBlank(config.getStr(field))){
                    config.set(field,StrUtil.str(rsa.decryptFromBase64(Base64.encode(config.getStr(field)),KeyType.PrivateKey),CharsetUtil.CHARSET_UTF_8));
                }
            }
        }
        /**
         * 设置数据源
         */
        /* 获得密码 */
        DruidPlugin dp = new DruidPlugin(config.getStr("jdbcUrl"), config.getStr("user"), config.getStr("password",""));
        dp.setMaxActive(config.getInt("maxActive",20));
        dp.setInitialSize(config.getInt("initialSize",1));
        dp.setMaxWait(config.getLong("maxWait",60000l));
        dp.setMinIdle(config.getInt("minIdle",1));
        dp.setTimeBetweenEvictionRunsMillis(config.getLong("timeBetweenEvictionRunsMillis",60000l));
        dp.setMinEvictableIdleTimeMillis(config.getLong("minEvictableIdleTimeMillis",300000l));
        dp.setValidationQuery(config.getStr("validationQuery","SELECT 'x'"));
        dp.setTestWhileIdle(config.getBool("testWhileIdle",true));
        dp.setTestOnBorrow(config.getBool("testOnBorrow",false));
        dp.setTestOnReturn(config.getBool("testOnReturn",false));
        return dp;
    }

    public boolean start() {
        try {
            /* 读取配置文件 */
            Setting config = new Setting(configPath);
            /* 获得分组 */
            if(config.getGroups()!=null && config.getGroups().size() > 0){
                /* 循环分组 */
                boolean isFirst = true;//定义标记，主要用于下面的model绑定
                for(String group : config.getGroups()){
                    Setting setting = config.getSetting(group);
                    DruidPlugin dp = createDruidPlugin(setting);
                    dp.start();//直接启动插件
                    /* 定义插件 */
                    ActiveRecordPlugin arp = new ActiveRecordPlugin(group,dp);
                    /* 设置有序排序字段 */
                    arp.setContainerFactory(new OrderedFieldContainerFactory());
                    /* 设置sql模板基础路径 */
                    arp.setBaseSqlTemplatePath(PathKit.getRootClassPath());
                    /* 设置开发模式 */
                    if(setting.get("devMode")!=null && setting.getStr("devMode").trim().length() > 0){
                        arp.setDevMode(setting.getBool("devMode"));
                    }
                    if(setting.get("devMode")!=null && setting.getStr("devMode").trim().length() > 0){
                        arp.setShowSql(setting.getBool("devMode"));
                    }
                    /* 设置 sql template 模板内置工具类 */
                    if(setting.get("sharedMethod")!=null && setting.getStr("sharedMethod").trim().length() > 0){
                        String[] classes = setting.getStrings("sharedMethod");
                        for(String cls : classes){
                            try {
                                Class _class = Class.forName(cls.trim());
                                arp.getEngine().addSharedMethod(_class.newInstance());
                            } catch (Exception e1) {
                                logger.error("[MysqlDataSourcePlugin] - 数据源 {} 加载 sharedMethod 方法失败，method : {} \r\n {}",group,cls, ExceptionUtil.getMessage(e1));
                            }
                        }
                    }
                    /**
                     * 扫描sql模板并加载
                     */
                    loadSqlTemplate(arp);
                    /**
                     * 扫描实体类并加载
                     */
                    Reflections _reflections = new Reflections(new ConfigurationBuilder().setUrls(ClasspathHelper.forPackage("")).filterInputsBy(new FilterBuilder().include(".+\\.model\\.[^\\.]+\\.class")));
                    Set<Class<?>> models = _reflections.getTypesAnnotatedWith(Model.class);
                    if(models!=null){
                        for(Class _m : models){
                            logger.debug("[MysqlDataSourcePlugin] - 正在加载模型 {}",_m.getName());
                            com.jfinal.annotation.Model m = (com.jfinal.annotation.Model) _m.getAnnotation(com.jfinal.annotation.Model.class);
                            /* 如果模型数据源为空，就要判断当前数据源是否为第一个 */
                            if((group.equals(m.ds())||(StrUtil.isBlank(m.ds()) && isFirst))){
                                if(StrUtil.isBlank(m.key())){
                                    arp.addMapping(m.table(), _m);
                                }else{
                                    arp.addMapping(m.table(),m.key(),_m);
                                }
                            }
                        }
                    }
                    arp.start();
                    isFirst = false;
                }
            }
        }catch (Exception e) {
            logger.error("[MysqlDataSourcePlugin] - 加载配置文件 {} 异常，初始化失败",configPath);
            return false;
        }
        logger.info("[MysqlDataSourcePlugin] - 插件初始化成功");
        return true;
    }

    public boolean stop() {
        return false;
    }

    /**
     * 加载SQL 模板文件
     * @param arp
     */
    public void loadSqlTemplate(ActiveRecordPlugin arp){
        Reflections reflections = null;
        if(stlRootPath == null){
            logger.debug("[MysqlDataSourcePlugin] - 采用默认方式获取stl查询地址 {}",ClasspathHelper.forPackage("/"));
            reflections = new Reflections(new ConfigurationBuilder().setUrls(ClasspathHelper.forPackage("/")).setScanners(new ResourcesScanner()));
        }else{
            try {
                reflections = new Reflections(new ConfigurationBuilder().setUrls(new URL("file:///"+stlRootPath)).setScanners(new ResourcesScanner()));
            } catch (Exception e) {
                logger.error("[MysqlDataSourcePlugin] - 初始化 sql template 加载根路径失败 : {}",stlRootPath);
            }
        }
        if(reflections!=null){
            Set<String> _sql = reflections.getResources(Pattern.compile(".*\\.stl"));
            /* 获得sqltemplate文件 */
            if(_sql!=null){
                for(String sql : _sql){
                    arp.addSqlTemplate(sql);
                    logger.debug("[MysqlDataSourcePlugin] - 加载 sql template 模板 : {}",sql);
                }
            }
        }
    }
}
